package org.bozo.im.service.core.roster.eclair;

import java.util.ArrayList;
import java.util.Hashtable;

import org.bozo.im.R;
import org.bozo.im.activity.Constants;
import org.bozo.im.common.types.Protocol;
import org.bozo.im.common.types.RawContact;
import org.bozo.im.common.utils.PresenceUtils;
import org.bozo.im.common.utils.ProtocolUtils;
import org.bozo.im.service.core.authenticator.EmptyAuthenticator;
import org.bozo.im.service.core.database.DatabaseHelper;
import org.bozo.im.service.core.roster.AbstractContactProvider;
import org.bozo.im.service.core.roster.ContactDesc;
import org.bozo.im.service.core.roster.ContactId;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.RawContacts.Data;
import android.provider.ContactsContract.RawContacts.Entity;
import android.util.Log;

public class ContactProvider extends AbstractContactProvider {

	private enum Action {INSERT, UPDATE, BULK_INSERT};

	private Context mContext;
	private DatabaseHelper mDatabase;
	private Hashtable<ContactId, Long> mIds;
	private Hashtable<Long, RawContact> mContacts;

	public ContactProvider(Context pContext, DatabaseHelper pDatabase) {
		mContext = pContext;
		mDatabase = pDatabase;
		mContacts = new Hashtable<Long, RawContact>();
	}

	@Override
	public void addContacts(ArrayList<ContactDesc> pDescs) {

		// ---------------------------------------------------------------------------------------
		Log.d(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.addContacts ---------- ");
		// ---------------------------------------------------------------------------------------
		
		ArrayList<ContentProviderOperation> lOps = new ArrayList<ContentProviderOperation>();

		Account lAccount = getAccount(mContext.getResources().getString(R.string.account_type));

		int lBackValueRow = 0;

		for (ContactDesc lCurrentDesc : pDescs) {

			Long lId = getId(ContactId.build(lCurrentDesc.protocol, lCurrentDesc.login));

			if (lId == null) {

				lOps.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
						.withValue(RawContacts.ACCOUNT_TYPE, lAccount.type)
						.withValue(RawContacts.ACCOUNT_NAME, lAccount.name)
						.build());

				int lProtocol = ProtocolUtils.toSystemProtocol(lCurrentDesc.protocol);

				ContentProviderOperation.Builder lIm = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
				.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, lBackValueRow)
				.withValue(Entity.MIMETYPE, Im.CONTENT_ITEM_TYPE)
				.withValue(Im.PROTOCOL, ProtocolUtils.toSystemProtocol(lCurrentDesc.protocol))
				.withValue(Im.DATA, lCurrentDesc.login);

				if (lProtocol == Im.PROTOCOL_CUSTOM)
					lIm.withValue(Im.CUSTOM_PROTOCOL, mContext.getResources().getString(lCurrentDesc.protocol.getRessourceId()));

				lOps.add(lIm.build());

				lOps.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
						.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, lBackValueRow)
						.withValue(Entity.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
						.withValue(StructuredName.DISPLAY_NAME, lCurrentDesc.displayName)
						.build());

				lBackValueRow += 3;
			}
		}

		try {
			mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, lOps);
		} catch (RemoteException e) {
			// ---------------------------------------------------------------------------------------
			Log.e(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.addContacts ---------- RemoteException: ", e);
			// ---------------------------------------------------------------------------------------
		} catch (OperationApplicationException e) {
			// ---------------------------------------------------------------------------------------
			Log.e(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.addContacts ---------- OperationApplicationException: ", e);
			// ---------------------------------------------------------------------------------------
		}
		
		for (ContactDesc lCurrentDesc : pDescs) {

			Long lId = getId(ContactId.build(lCurrentDesc.protocol, lCurrentDesc.login));
			loadContact(lId);
		}
	}

	/**
	 * Retrieve the system ID that matches this contact description
	 * @param pId
	 * @return
	 */
	private Long getId(ContactId pId) {

		if (mIds == null)
			mIds = loadIdsFromDb();

		if (!mIds.contains(pId)) {

			int lIntProtocol = ProtocolUtils.toSystemProtocol(pId.getProtocol());

			String[] lParams;
			StringBuilder lQuery = new StringBuilder();
			lQuery.append(RawContactsEntity.MIMETYPE).append("=? AND ")
				.append(Im.PROTOCOL).append("=? AND ")
				.append(Im.DATA).append("=?");

			if (lIntProtocol == Im.PROTOCOL_CUSTOM) {
				lQuery.append(" AND ").append(Im.CUSTOM_PROTOCOL).append("=?");
				lParams = new String[] {Im.CONTENT_ITEM_TYPE, String.valueOf(lIntProtocol), pId.getHandle(), pId.getProtocol().name()};
			} else {
				lParams = new String[] {Im.CONTENT_ITEM_TYPE, String.valueOf(lIntProtocol), pId.getHandle()};
			}

			Cursor lCursor = mContext.getContentResolver().query(
					RawContactsEntity.CONTENT_URI,
					new String[] {RawContactsEntity.CONTACT_ID},
					lQuery.toString(),
					lParams,
					null);

			// ---------------------------------------------------------------------------------------
			Log.d(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.getId ---------- " + lCursor.getCount());
			// ---------------------------------------------------------------------------------------
			
			if (lCursor.moveToFirst()) {

				long lId = lCursor.getLong(lCursor.getColumnIndex(RawContactsEntity.CONTACT_ID));

				SQLiteDatabase lDatabase = mDatabase.getWritableDatabase();
				ContentValues lValues = new ContentValues();
				lValues.put(DatabaseHelper.CONTACT_FIELD_ID, lId);
				lValues.put(DatabaseHelper.CONTACT_FIELD_HANDLE, pId.getHandle());
				lValues.put(DatabaseHelper.CONTACT_FIELD_PROTOCOL, pId.getProtocol().name());
				lDatabase.insert(DatabaseHelper.CONTACT_TABLE_NAME, null, lValues);

				mIds.put(pId, lId);
			}
		}

		return mIds.get(pId);
	}

	/**
	 * Loads a contact from the Contact Provider v5
	 * @param pProtocol
	 * @param pHandle
	 * @param pDisplayName
	 * @return
	 */
	@Override
	public RawContact getContact(Protocol pProtocol, String pHandle) {

		// ---------------------------------------------------------------------------------------
		Log.d(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.getContact ---------- ");
		// ---------------------------------------------------------------------------------------
		
		RawContact lRes = null;
		Long lId = getId(ContactId.build(pProtocol, pHandle));

		if (lId != null) {
			loadContact(lId);
			lRes = mContacts.get(lId);
		}

		return lRes;
	}

	/**
	 * Load the list of the contacts managed by the Unified IM Client
	 * @return
	 */
	@Override	
	public ArrayList<RawContact> getContacts() {
		// ---------------------------------------------------------------------------------------
		Log.d(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.getContacts ---------- " + mContacts.size());
		// ---------------------------------------------------------------------------------------
		
		return new ArrayList<RawContact>(mContacts.values());
	}

	/**
	 * Update the contact in the Contact Provider v5
	 */
	@Override
	public void updateContact(RawContact pContact, RawContact.Field pField) {

		ContentValues lData = new ContentValues();
		DBRunner lRunner = null;

		switch (pField) {
		case DISPLAY_NAME:
			Uri lRawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, pContact.getSystemId());
			Uri lDataUri = Uri.withAppendedPath(lRawContactUri, Data.CONTENT_DIRECTORY);

			lData.put(StructuredName.DISPLAY_NAME, pContact.getDisplayName());

			lRunner = new DBRunner(Action.UPDATE,
					lDataUri,
					new ContentValues[]{lData},
					Entity.MIMETYPE + "=?",
					new String[] {StructuredName.CONTENT_ITEM_TYPE});

			break;

		case PRESENCE:
			int lProtocol = ProtocolUtils.toSystemProtocol(pContact.getProtocol());
			int lPresence = PresenceUtils.toSystemPresence(pContact.getPresence());

			lData.put(StatusUpdates.PRESENCE, lPresence);
			lData.put(StatusUpdates.IM_HANDLE, pContact.getId());
			lData.put(StatusUpdates.PROTOCOL, lProtocol);
			if (lProtocol == Im.PROTOCOL_CUSTOM)
				lData.put(StatusUpdates.CUSTOM_PROTOCOL, mContext.getResources().getString(pContact.getProtocol().getRessourceId()));

			lRunner = new DBRunner(Action.INSERT,
					StatusUpdates.CONTENT_URI, 
					new ContentValues[]{lData},
					null, 
					null);

			break;
		}

		Thread lThread = new Thread(lRunner);
		lThread.start();
	}

	private void loadContact(long pId) {

		// ---------------------------------------------------------------------------------------
		Log.d(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.eclair.ContactProvider.loadContact ---------- ");
		// ---------------------------------------------------------------------------------------
		
		if (!mContacts.contains(pId)) {

			RawContact lContact;
			Uri lRawContactUri;
			Cursor lRawCursor;
			ContentResolver lResolver = mContext.getContentResolver();

			lRawContactUri = ContentUris.withAppendedId(RawContactsEntity.CONTENT_URI, pId);

			lContact = new RawContact();
			lContact.setSystemId(pId);

			lRawCursor = lResolver.query(
					lRawContactUri,
					new String[] {RawContactsEntity.CONTACT_ID, Im.PROTOCOL, Im.DATA}, 
					Data.MIMETYPE + "=?",
					new String[] {Im.CONTENT_ITEM_TYPE}, 
					null);

			// Loading the data related to the aggregation
			if (lRawCursor.moveToFirst()) {
				int lIdIndex = lRawCursor.getColumnIndex(RawContactsEntity.CONTACT_ID);
				int lProtocolIndex = lRawCursor.getColumnIndex(Im.PROTOCOL);
				int lHandleIndex = lRawCursor.getColumnIndex(Im.DATA);
				lContact.setContactId(lRawCursor.getLong(lIdIndex));
				lContact.setProtocol(ProtocolUtils.fromSystemProtocol(lRawCursor.getInt(lProtocolIndex)));
				lContact.setId(lRawCursor.getString(lHandleIndex));
			}

			lRawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, pId);
			lRawContactUri = Uri.withAppendedPath(lRawContactUri, Data.CONTENT_DIRECTORY);

			// Loading the data related to the display name
			lRawCursor = lResolver.query(
					lRawContactUri,
					new String[] {StructuredName.DISPLAY_NAME},
					Data.MIMETYPE + "=?", 
					new String[]{StructuredName.CONTENT_ITEM_TYPE},
					null);

			if (lRawCursor.moveToFirst()) {
				int lDisplayNameIndex = lRawCursor.getColumnIndex(StructuredName.DISPLAY_NAME);
				lContact.setDisplayName(lRawCursor.getString(lDisplayNameIndex));
			}

			mContacts.put(pId, lContact);
		}
	}


	/**
	 * Retrieve the Account associated to the Unified IM Client
	 * @param pAccountType
	 * @return
	 */
	private Account getAccount(String pAccountType) {

		Account lRes = null;

		AccountManager lManager = AccountManager.get(mContext);
		Account[] lAccounts = lManager.getAccountsByType(pAccountType);

		// If there is no account registered for this application, then we create one
		if (lAccounts.length == 0) {
			try {
				AccountManagerFuture<Bundle> lAccountRes = AccountManager.get(mContext).addAccount(
						pAccountType, null, null, null, null, null, null);

				lRes = (Account)lAccountRes.getResult().getParcelable(EmptyAuthenticator.EMPTY_ACCOUNT);
			} catch (Exception e) {
				// ---------------------------------------------------------------------------------------
				Log.e(Constants.ACTIVITY_TAG, "org.bozo.im.service.core.roster.Roster.getAccount ---------- Exception: ", e);
				// ---------------------------------------------------------------------------------------
			}
		} else {
			lRes = lAccounts[0];
		}

		return lRes;
	}

	/**
	 * Load the IDs from the private database
	 * @return
	 */
	private Hashtable<ContactId, Long> loadIdsFromDb() {

		Hashtable<ContactId, Long> lRes = new Hashtable<ContactId, Long>();

		SQLiteDatabase lDatabase = mDatabase.getReadableDatabase();
		Cursor lCursor = lDatabase.query(DatabaseHelper.CONTACT_TABLE_NAME, null, null, null, null, null, null);

		if (lCursor.moveToFirst()) {

			int lIdIndex = lCursor.getColumnIndex(DatabaseHelper.CONTACT_FIELD_ID);
			int lProtocolIndex = lCursor.getColumnIndex(DatabaseHelper.CONTACT_FIELD_PROTOCOL);
			int lHandleIndex = lCursor.getColumnIndex(DatabaseHelper.CONTACT_FIELD_HANDLE);

			do {
				Protocol lProtocol = Protocol.valueOf(lCursor.getString(lProtocolIndex));
				String lHandle = lCursor.getString(lHandleIndex);
				Long lId = lCursor.getLong(lIdIndex);

				lRes.put(ContactId.build(lProtocol, lHandle), lId);

			} while (lCursor.moveToNext());
		}

		lCursor.close();

		return lRes;
	}

	private class DBRunner implements Runnable {

		private Action mAction;
		private Uri mUri;
		private ContentValues[] mValues;
		private String mWhere;
		private String[] mSelectionArgs;

		public DBRunner(Action pAction, Uri pUri, ContentValues[] pValues, String pWhere, String[] pSelectionArgs) {
			mAction = pAction;
			mUri = pUri;
			mValues = pValues;
			mWhere = pWhere;
			mSelectionArgs = pSelectionArgs;
		}

		public void run() {
			ContentResolver lResolver = mContext.getContentResolver();

			switch (mAction) {
			case UPDATE:
				lResolver.update(mUri, mValues[0], mWhere, mSelectionArgs);
				break;
			case INSERT:
				lResolver.insert(mUri, mValues[0]);
				break;
			case BULK_INSERT:
				lResolver.bulkInsert(mUri, mValues);
				break;
			}
		}
	}
}
